package pl.edu.agh.ssm.monitor.visualization;

import java.util.HashMap;
import java.util.Iterator;

import pl.edu.agh.ssm.monitor.data.RTSPPacket;
import pl.edu.agh.ssm.monitor.data.RTSPPacketType;
import pl.edu.agh.ssm.monitor.data.SessionConnection;
import pl.edu.agh.ssm.monitor.data.SessionMediaType;
import pl.edu.agh.ssm.monitor.data.SessionNode;
import pl.edu.agh.ssm.monitor.data.SessionNodeType;
//import pl.edu.agh.ssm.monitor.visualization.test.NetGraphVisualization;
import prefuse.Constants;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Tuple;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;

/**
 * 
 * @author Monika Nawrot
 * 
 */

public class NetGraph {

	/*
	 * Instance graph visualization
	 */
	private final Visualization visualisation;

	/*
	 * Aggregated graph
	 */
	private Graph graph;

	/*
	 * Aggregate Table
	 */
	AggregateTable aggregate;

	/*
	 * Graph groups
	 */
	public static final String GRAPH = "graph";
	public static final String NODES = "graph.nodes";
	public static final String EDGES = "graph.edges";
	public static final String AGGR = "aggregates";

	private static final String edge_decorators = "edgeDeco";

	private static final Schema DECORATOR_SCHEMA = PrefuseLib
			.getVisualItemSchema();
	static {
		DECORATOR_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
		DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(128));
		DECORATOR_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma",
				16));
	}

	/*
	 * Graph visualization
	 */
	@SuppressWarnings("unused")
	private VisualGraph vg;

	private HashMap<String, SessionNode> nodesMap = null;
	private HashMap<String, SessionConnection> connectionsMap = null;

	public NetGraph(Visualization visualisation) {
		this.visualisation = visualisation;

		graph = new Graph();

		createDataTypes();
		createAggregate();
		createVisualisation();

		addRenderers();
		addColors();

		// TODO remove
//		initTest();
	}

	/*
	 * Create visualization for graph
	 */
	private void createVisualisation() {
		vg = visualisation.addGraph(GRAPH, graph);
		visualisation.setInteractive(EDGES, null, false);
		visualisation.setValue(NODES, null, VisualItem.SHAPE, new Integer(
				Constants.SHAPE_ELLIPSE));
		visualisation.setValue(EDGES, null, VisualItem.SHAPE, new Integer(
				Constants.SHAPE_ELLIPSE));
	}

	/*
	 * Create aggregate
	 */
	private void createAggregate() {
		aggregate = visualisation.addAggregates(AGGR);
		aggregate.addColumn(VisualItem.POLYGON, float[].class);

		aggregate.addColumn("nodeType", SessionNodeType.class);
		aggregate.addColumn("nodeAddress", String.class);
		aggregate.addColumn("id", String.class);
	}

	/*
	 * Create data types for nodes and edges
	 */
	private void createDataTypes() {
		/*
		 * Node labels
		 */
		graph.addColumn("nodeType", SessionNodeType.class);
		graph.addColumn("nodeAddress", String.class);
		graph.addColumn("id", String.class);
		graph.addColumn("image", String.class);

		/*
		 * Edge labels
		 */
		graph.addColumn("edgeMedia", SessionMediaType.class);
		graph.addColumn("edgeAddress", String.class);
		graph.addColumn("edgePort", Integer.class);
		graph.addColumn("edgeImage", String.class);
	}

	/*
	 * Add node and edge renderers to graph
	 */
	private void addRenderers() {

		DefaultRendererFactory drf = new DefaultRendererFactory();
		
		/*
		 * Node renderer
		 */
		LabelRenderer nodeLabelRenderer = new LabelRenderer("nodeAddress", "image");
		nodeLabelRenderer.setRoundedCorner(18, 18);
		nodeLabelRenderer.setMaxImageDimensions(70, 70);

		/*
		 * Edge renderer
		 */
		EdgeRenderer edgeRenderer = new MultipleEdgeRenderer(
				Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		edgeRenderer.setDefaultLineWidth(2);

		LabelRenderer edgeLabelRenderer = new LabelRenderer("edgeAddress",
				"edgeImage");
		edgeLabelRenderer.setMaxImageDimensions(30, 30);
		drf.add(new InGroupPredicate(edge_decorators), edgeLabelRenderer);

		/*
		 * Aggregates renderer
		 */
		Renderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
		((PolygonRenderer) polyR).setCurveSlack(0.15f);
				
		// node renderer
		drf.setDefaultRenderer(nodeLabelRenderer);
		// edge renderer
		drf.setDefaultEdgeRenderer(edgeRenderer);
		// aggregate renderer
		drf.add("ingroup('aggregates')", polyR);

		visualisation.setRendererFactory(drf);
	}

	/*
	 * Add node and edge colors to graph
	 */
	private void addColors() {

		/*
		 * Create colour pallette for nodes
		 */
		int[] nodeTypePallette = new int[] { ColorLib.rgb(204, 255, 204),
				ColorLib.rgb(153, 204, 255), ColorLib.rgb(255, 204, 255),
				ColorLib.rgb(204, 204, 204), ColorLib.rgb(255, 204, 204),
				ColorLib.rgb(204, 204, 255) };

		DataColorAction nFill = new DataColorAction(NODES, "nodeType",
				Constants.NOMINAL, VisualItem.FILLCOLOR, nodeTypePallette);

		/*
		 * Create text labels for nodes
		 */
		ColorAction nText = new ColorAction("graph.nodes",
				VisualItem.TEXTCOLOR, ColorLib.gray(0));

		/*
		 * Create colour for aggregates
		 */
		ColorAction aStroke = new ColorAction(AGGR, VisualItem.STROKECOLOR);
		aStroke.setDefaultColor(ColorLib.gray(200));
		aStroke.add("_hover", ColorLib.rgb(153, 102, 102));
		
		/*
		 * Create edge decorators
		 */
		DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(128));
		visualisation.addDecorators(edge_decorators, EDGES, DECORATOR_SCHEMA);

		ActionList colors = new ActionList();
		colors.add(new FontAction(NODES, FontLib.getFont("Tahoma", 16)));
		colors.add(nFill);
		colors.add(nText);
		colors.add(aStroke);
		
		colors.add(new EdgeLabelLayout(edge_decorators));

		ActionList layout = new ActionList(Activity.INFINITY);
		layout.add(colors);
		layout.add(new ForceDirectedLayout(GRAPH, true));
		layout.add(new RepaintAction());
		layout.add(new AggregateLayout(AGGR));

		visualisation.putAction("layout", layout);
	}

	/*
	 * Locate a node on the visualization panel
	 */
	private void locateElement(Node node) {

		int counter = 0;

		double groupX = 0;
		double groupY = 0;

		int displacement = 50;
		TupleSet ts = visualisation.getVisualGroup(NODES);

		@SuppressWarnings( { "unchecked", "unchecked" })
		Iterator<Tuple> ti = ts.tuples();
		while (ti.hasNext()) {
			Tuple tuple = ti.next();
			VisualItem nodeItem = visualisation.getVisualItem(NODES, tuple);

			if (tuple.getString("id").equals(node.getString("id")))
				continue;

			if (tuple.getString("nodeAddress").equals(
					node.getString("nodeAddress"))) {

				if (counter == 0) {
					groupX = nodeItem.getX();
					groupY = nodeItem.getY();
				}

				counter++;
			}
		}

		VisualItem newNode = visualisation.getVisualItem(NODES, node);

		if (counter != 0) {
			newNode.setX(groupX + displacement * counter);
			newNode.setY(groupY + displacement * counter);

		} else {
			double x = Math.random() * visualisation.getDisplay(0).getHeight();
			double y = Math.random() * visualisation.getDisplay(0).getWidth();

			newNode.setX(x);
			newNode.setY(y);
		}

		newNode.setFixed(true);
	}

	/*
	 * Add to aggregate
	 */
	@SuppressWarnings("unchecked")
	private void addToAggregate(Node node) {
		TupleSet ts;
		Iterator<Tuple> ti;

		boolean unknownAddress = true;

		ts = visualisation.getVisualGroup(NODES);
		ti = ts.tuples();
		while (ti.hasNext()) {
			Tuple tuple = ti.next();

			if (!tuple.isValid())
				continue;
			
			Iterator<AggregateItem> items = aggregate.getAggregates(tuple);
			if (items == null)
				continue;
			
			while (items.hasNext()) {
				AggregateItem item = items.next();

				if (item.getString("nodeAddress").equals(
						node.getString("nodeAddress"))) {

					VisualItem nodeItem = visualisation.getVisualItem(NODES,
							node);
					item.addItem((VisualItem) nodeItem);

					unknownAddress = false;
				}
				break;
			}

		}

		if (unknownAddress) {
			ts = visualisation.getVisualGroup(NODES);
			ti = ts.tuples();

			while (ti.hasNext()) {
				Tuple tuple = ti.next();
				VisualItem nodeItem = visualisation.getVisualItem(NODES, tuple);

				if (nodeItem.getString("nodeAddress").equals(
						node.getString("nodeAddress"))) {

					AggregateItem aitem = (AggregateItem) aggregate.addItem();
					aitem.setString("nodeAddress", node
							.getString("nodeAddress"));
					aitem.addItem((VisualItem) nodeItem);
					break;
				}
			}
		}
	}

	/*
	 * Remove from aggregate
	 */
	@SuppressWarnings("unchecked")
	private void removeFromAggregate(Node node) {
		
		aggregate.clear();
		
		Iterator<Node> nodes = graph.nodes();
		while (nodes.hasNext()) {
			addToAggregate(nodes.next());
		}

	}

	/***************************************************************************
	 * 
	 * Node operations
	 * 
	 */

	public void newNode(SessionNode node) {
		System.out.println("NET_GRAPH: " + node.getId() + " to graph: " + this);
		Node n = graph.addNode();
		n.set("nodeType", node.getNodeType());
		n.set("nodeAddress", node.getAddress().getHostAddress());
		n.set("id", node.getId());

		setNodeIcon(node, n);

		addToAggregate(n);
		locateElement(n);

		nodesMap.put(n.getString("id"), node);
	}

	/*
	 * Set node icon
	 */
	private String NODE_IMAGES_PATH = "pl/edu/agh/ssm/monitor/resources/images/";

	private void setNodeIcon(SessionNode node, Node n) {
		if (node.getNodeType() == SessionNodeType.SENDER)
			n.set("image", NODE_IMAGES_PATH + "sender.gif");
		else if (node.getNodeType() == SessionNodeType.RECEIVER)
			n.set("image", NODE_IMAGES_PATH + "receiver.gif");
		else if (node.getNodeType() == SessionNodeType.SENDER_AND_RECEIVER)
			n.set("image", NODE_IMAGES_PATH + "sender_and_receiver.gif");
		else if (node.getNodeType() == SessionNodeType.MULTIPLEXER)
			n.set("image", NODE_IMAGES_PATH + "multiplexer.gif");
		else if (node.getNodeType() == SessionNodeType.SERVER)
			n.set("image", NODE_IMAGES_PATH + "server.gif");
		else if (node.getNodeType() == SessionNodeType.UNKNOWN)
			n.set("image", NODE_IMAGES_PATH + "unknown.gif");
	}

	/*
	 * Set edge icon
	 */
	private String EDGE_IMAGES_PATH = "pl/edu/agh/ssm/monitor/resources/rtsp_images/";

	private void setEdgeIcon(SessionConnection connection, Edge e) {

		RTSPPacket packet = connection.getLastRtspPacket();

		if (packet == null) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "none.gif");
		} else if (packet.getType() == RTSPPacketType.ANNOUNCE) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "announce.gif");
		} else if (packet.getType() == RTSPPacketType.DESCRIBE) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "describe.gif");
		} else if (packet.getType() == RTSPPacketType.GET_PARAMETER) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "get_parameter.gif");
		} else if (packet.getType() == RTSPPacketType.OPTIONS) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "options.gif");
		} else if (packet.getType() == RTSPPacketType.PAUSE) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "pause.gif");
		} else if (packet.getType() == RTSPPacketType.PLAY) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "play.gif");
		} else if (packet.getType() == RTSPPacketType.RECORD) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "record.gif");
		} else if (packet.getType() == RTSPPacketType.REDIRECT) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "redirect.gif");
		} else if (packet.getType() == RTSPPacketType.SET_PARAMETER) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "set_parameter.gif");
		} else if (packet.getType() == RTSPPacketType.SETUP) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "setup.gif");
		} else if (packet.getType() == RTSPPacketType.TEARDOWN) {
			e.set("edgeImage", EDGE_IMAGES_PATH + "teardown.gif");
		}

	}

	@SuppressWarnings("unchecked")
	public void removeNode(SessionNode node) {
		Iterator<Node> it = graph.nodes();
		while (it.hasNext()) {
			Node n = it.next();
			if (n.get("id").toString().equals(node.getId())) {
				nodesMap.remove(n.getString("id"));
				graph.removeNode(n);
				removeFromAggregate(n);
				break;
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void updateNode(SessionNode node) {
		Iterator<Node> it = graph.nodes();
		while (it.hasNext()) {
			Node n = it.next();
			if (n.get("id").toString().equals(node.getId())) {
				n.set("nodeType", node.getNodeType());
				n.set("nodeAddress", node.getAddress().getHostAddress());
				setNodeIcon(node, n);
				
				removeFromAggregate(n);
				addToAggregate(n);
				nodesMap.put(n.getString("id"), node);
				break;
			}
		}
	}

	/***************************************************************************
	 * 
	 * Edge operations
	 * 
	 */

	@SuppressWarnings("unchecked")
	public void newSessionConnection(SessionConnection connection) {
		Node sender = null;
		Node receiver = null;

		Iterator<Node> it = graph.nodes();
		while (it.hasNext()) {
			Node n = it.next();

			if (n.get("id").toString().equals(connection.getSender().getId())) {
				sender = n;
			} else if (n.get("id").toString().equals(
					connection.getReceiver().getId())) {
				receiver = n;
			}
		}

		if (sender == null || receiver == null) {
			return;
		}

		Edge edge = graph.addEdge(sender, receiver);
		edge.set("id", connection.getId());
		edge.set("edgeMedia", connection.getMediaDesc().getMediaType());
		edge.set("edgeAddress", connection.getConnectionAddress()
				.getHostAddress());
		edge.set("edgePort", connection.getConnectionPort());

		setEdgeIcon(connection, edge);

		connectionsMap.put(connection.getId(), connection);
	}

	@SuppressWarnings("unchecked")
	public void removeSessionConnection(SessionConnection connection) {
		Iterator<Edge> edges = graph.edges();
		while (edges.hasNext()) {
			Edge edge = edges.next();
			if (edge.get("id").equals(connection.getId())) {
				graph.removeEdge(edge);
				connectionsMap.remove(connection.getId());
				break;
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void updateSessionConnection(SessionConnection connection) {
		Iterator<Edge> edges = graph.edges();
		while (edges.hasNext()) {
			Edge edge = edges.next();
			if (edge.get("id").equals(connection.getId())) {
				edge.set("edgeMedia", connection.getMediaDesc().getMediaType());
				edge.set("edgeAddress", connection.getConnectionAddress()
						.getHostAddress());
				edge.set("edgePort", connection.getConnectionPort());
				
				setEdgeIcon(connection, edge);
				
				connectionsMap.put(connection.getId(), connection);
				break;
			}
		}
	}

	public void setNodesMap(HashMap<String, SessionNode> nodesMap) {
		this.nodesMap = nodesMap;
	}

	public void setConnectionsMap(
			HashMap<String, SessionConnection> connectionsMap) {
		this.connectionsMap = connectionsMap;
	}

	/***************************************************************************
	 * 
	 * Test data
	 * 
	 */

//	private void initTest() {
//
//		NetGraphVisualization ngv = new NetGraphVisualization();
//		ngv.setGraph(this);
//		new Thread(ngv).start();
//	}

}
